/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
package org.unidata.mdm.dq.core.service.impl;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Nullable;

import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.context.UserLibraryGetContext;
import org.unidata.mdm.core.dto.UserLibraryResult;
import org.unidata.mdm.core.service.UserLibraryService;
import org.unidata.mdm.core.type.libraries.UserLibrariesListener;
import org.unidata.mdm.dq.core.exception.DataQualityExceptionIds;
import org.unidata.mdm.dq.core.service.impl.function.groovy.GroovyCleanseFunctionLibrary;
import org.unidata.mdm.dq.core.service.impl.function.java.JavaCleanseFunctionLibrary;
import org.unidata.mdm.dq.core.service.impl.function.python.PythonCleanseFunctionLibrary;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunction;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunctionLibrary;
import org.unidata.mdm.dq.core.type.model.instance.LibraryFunctionElement;
import org.unidata.mdm.system.exception.PlatformFailureException;
import org.unidata.mdm.system.service.ModuleService;

/**
 * @author Mikhail Mikhailov on Feb 3, 2021
 */
@Component
public class CleanseFunctionCacheComponent implements UserLibrariesListener {
    /**
     * A ULS instance.
     */
    private final UserLibraryService userLibraryService;
    /**
     * A MS instance.
     */
    private final ModuleService moduleService;
    /**
     * Local libraries cache.
     */
    private Map<String, CleanseFunctionLibrary> libraries = new ConcurrentHashMap<>();
    /**
     * Constructor.
     */
    @Autowired
    public CleanseFunctionCacheComponent(
            final UserLibraryService userLibraryService,
            final ModuleService moduleService) {
        super();
        this.userLibraryService = userLibraryService;
        this.userLibraryService.addListener(this);
        this.moduleService = moduleService;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void libraryUpserted(String storage, String name, String version) {
        // Just remove the library, if it exists,
        // causing it to be loaded once again on the next use.
        libraryRemoved(storage, name, version);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void libraryRemoved(String storage, String name, String version) {

        String id = CleanseFunctionLibrary.toName(storage, name, version);
        CleanseFunctionLibrary l = libraries.remove(id);

        if (Objects.nonNull(l)) {
            l.cleanup();
        }
    }

    @Nullable
    public Pair<CleanseFunction, Exception> find(String storageId, LibraryFunctionElement el) {

        try {
            String id = CleanseFunctionLibrary.toName(storageId, el.getLibrary(), el.getVersion());
            CleanseFunctionLibrary cfl = libraries.computeIfAbsent(id, k -> load(storageId, el.getLibrary(), el.getVersion()));

            return Pair.of(cfl.lookup(el), null);
        } catch (Exception exc) {
            return Pair.of(null, exc);
        }
    }

    @Nullable
    public CleanseFunctionLibrary find(String storageId, String library, String version) {

        try {
            String id = CleanseFunctionLibrary.toName(storageId, library, version);
            return libraries.computeIfAbsent(id, k -> load(storageId, library, version));
        } catch (Exception exc) {
            return null;
        }
    }

    private CleanseFunctionLibrary load(String storageId, String libraryName, String libraryVersion) {

        UserLibraryResult ulr = userLibraryService.get(UserLibraryGetContext.builder()
                .storageId(storageId)
                .filename(libraryName)
                .version(libraryVersion)
                .withData(true)
                .build());

        if (Objects.nonNull(ulr)) {

            switch (ulr.getMimeType()) {
            case JAR_FILE:
                return new JavaCleanseFunctionLibrary(this.moduleService, ulr);
            case GROOVY_SOURCE:
                return new GroovyCleanseFunctionLibrary(ulr);
            case PYTHON_CODE:
            case PYTHON_SOURCE:
                return new PythonCleanseFunctionLibrary(ulr);
            default:
                break;
            }
        }

        throw new PlatformFailureException("Library [{}:{}] not found.",
                DataQualityExceptionIds.EX_DQ_LIBRARY_NOT_FOUND,
                libraryName, libraryVersion);
    }
}
